/*************************************************************************
 *                  HONGLING CAPITAL CONFIDENTIAL AND PROPRIETARY
 *
 *                COPYRIGHT (C) HONGLING CAPITAL CORPORATION 2012
 *    ALL RIGHTS RESERVED BY HONGLING CAPITAL CORPORATION. THIS PROGRAM
 * MUST BE USED  SOLELY FOR THE PURPOSE FOR WHICH IT WAS FURNISHED BY
 * HONGLING CAPITAL CORPORATION. NO PART OF THIS PROGRAM MAY BE REPRODUCED
 * OR DISCLOSED TO OTHERS,IN ANY FORM, WITHOUT THE PRIOR WRITTEN
 * PERMISSION OF HONGLING CAPITAL CORPORATION. USE OF COPYRIGHT NOTICE
 DOES NOT EVIDENCE PUBLICATION OF THE PROGRAM.
 *                  HONGLING CAPITAL CONFIDENTIAL AND PROPRIETARY
 *************************************************************************/

package com.springboot.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 手机格式校验工具类。
 *
 * @author hongchao zhao at 2019-3-19 14:24
 */
public class ValidationUtils {

    /** 电话格式验证 **/
    private static final String PHONE_CALL_PATTERN = "^(\\(\\d{3,4}\\)|\\d{3,4}-)?\\d{7,8}(-\\d{1,4})?$";

    /**
     * 中国电信号码格式验证 手机段： 133,153,180,181,189,177,1700
     * **/
    private static final String CHINA_TELECOM_PATTERN = "(^1(33|53|77|8[019])\\d{8}$)|(^1700\\d{7}$)";

    /**
     * 中国联通号码格式验证 手机段：130,131,132,155,156,185,186,145,176,1709
     * **/
    private static final String CHINA_UNICOM_PATTERN = "(^1(3[0-2]|4[5]|5[56]|7[6]|8[56])\\d{8}$)|(^1709\\d{7}$)";

    /**
     * 中国移动号码格式验证
     * 手机段：134,135,136,137,138,139,150,151,152,157,158,159,182,183,184
     * ,187,188,147,178,1705
     * **/
    private static final String CHINA_MOBILE_PATTERN = "(^1(3[4-9]|4[7]|5[0-27-9]|7[8]|8[2-478])\\d{8}$)|(^1705\\d{7}$)";

    /**
     * 身份证号
     */
    private static final Pattern PATTERN_ID_CARD = Pattern.compile("^\\w{15}|\\w{18}$");
    /**
     * 二代身份证
     */
    private static final Pattern PATTERN_ID_CARD_V2 = Pattern.compile("^\\w{18}$");
    /**
     * 邮箱
     */
    private static final Pattern PATTERN_EMAIL = Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*");
    /**
     * QQ
     */
    private static final Pattern PATTERN_QQ = Pattern.compile("^\\d{5,11}");

    private static final Map<String, String> AREA_CODES_FOR_ID_CARD = new HashMap<String, String>(){{
        put("11", "北京");
        put("12", "天津");
        put("13", "河北");
        put("14", "山西");
        put("15", "内蒙古");
        put("21", "辽宁");
        put("22", "吉林");
        put("23", "黑龙江");
        put("31", "上海");
        put("32", "江苏");
        put("33", "浙江");
        put("34", "安徽");
        put("35", "福建");
        put("36", "江西");
        put("37", "山东");
        put("41", "河南");
        put("42", "湖北");
        put("43", "湖南");
        put("44", "广东");
        put("45", "广西");
        put("46", "海南");
        put("50", "重庆");
        put("51", "四川");
        put("52", "贵州");
        put("53", "云南");
        put("54", "西藏");
        put("61", "陕西");
        put("62", "甘肃");
        put("63", "青海");
        put("64", "宁夏");
        put("65", "新疆");
        put("71", "台湾");
        put("81", "香港");
        put("82", "澳门");
        put("91", "国外");
    }};

    /**
     * 验证电话号码的格式
     *
     * @author LinBilin
     * @param str
     *            校验电话字符串
     * @return 返回true,否则为false
     */
    public static boolean isPhoneCallNum(String str) {

        return !(str == null || "".equals(str.trim())) && match(
                PHONE_CALL_PATTERN, str);
    }

    /**
     * 验证【电信】手机号码的格式
     *
     * @author LinBilin
     * @param str
     *            校验手机字符串
     * @return 返回true,否则为false
     */
    public static boolean isChinaTelecomPhoneNum(String str) {
        return !(str == null || "".equals(str.trim())) && match(
                CHINA_TELECOM_PATTERN, str);
    }

    /**
     * 验证【联通】手机号码的格式
     *
     * @author LinBilin
     * @param str
     *            校验手机字符串
     * @return 返回true,否则为false
     */
    public static boolean isChinaUnicomPhoneNum(String str) {
        return !(str == null || "".equals(str.trim())) && match(
                CHINA_UNICOM_PATTERN, str);
    }

    /**
     * 验证【移动】手机号码的格式
     *
     * @author LinBilin
     * @param str
     *            校验手机字符串
     * @return 返回true,否则为false
     */
    public static boolean isChinaMobilePhoneNum(String str) {
        return !(str == null || "".equals(str.trim())) && match(
                CHINA_MOBILE_PATTERN, str);
    }


    /**
     * 验证手机和电话号码的格式
     *
     * @author LinBilin
     * @param str
     *            校验手机字符串
     * @return 返回true,否则为false
     */
    public static boolean isPhoneNum(String str) {
        // 如果字符串为空，直接返回false
        if (str == null || "".equals(str.trim())) {
            return false;
        } else {
            int comma = str.indexOf(",");// 是否含有逗号
            int caesuraSign = str.indexOf("、");// 是否含有顿号
            int space = str.trim().indexOf(" ");// 是否含有空格
            if (comma == -1 && caesuraSign == -1 && space == -1) {
                // 如果号码不含分隔符,直接验证
                str=str.trim();
                return isPhoneCallNum(str) || isChinaTelecomPhoneNum(str)
                        || isChinaUnicomPhoneNum(str) || isChinaMobilePhoneNum(str);
            } else {
                // 号码含分隔符,先把分隔符统一处理为英文状态下的逗号
                if (caesuraSign != -1) {
                    str=str.replaceAll("、", ",");
                }
                if (space != -1) {
                    str=str.replaceAll(" ", ",");
                }

                String[] phoneNumArr = str.split(",");
                //遍历验证
                for (String temp : phoneNumArr) {
                    temp=temp.trim();
                    if (isPhoneCallNum(temp) || isChinaTelecomPhoneNum(temp)
                            || isChinaUnicomPhoneNum(temp)
                            || isChinaMobilePhoneNum(temp)) {
                        continue;
                    } else {
                        return false;
                    }
                }
                return true;
            }
        }
    }

    /**
     * 执行正则表达式
     *
     * @param pat
     *            表达式
     * @param str
     *            待验证字符串
     * @return 返回true,否则为false
     */
    private static boolean match(String pat, String str) {
        Pattern pattern = Pattern.compile(pat);
        Matcher match = pattern.matcher(str);
        return match.find();
    }


    /**
     * 判断是否通过手机浏览器访问。
     * @param request   {@link HttpServletRequest}。
     * @return  {@link Boolean}。
     */
    public static boolean accessFromMobile(HttpServletRequest request) {
        String[] mobileAgents = {"iphone", "ipad", "android", "phone", "mobile",
                "wap", "netfront", "java", "opera mobi", "opera mini", "ucweb",
                "windows ce", "symbian", "series", "webos", "sony", "vivo",
                "blackberry", "dopod", "nokia", "samsung", "palmsource", "xda",
                "pieplus", "meizu", "midp", "cldc", "motorola", "foma",
                "docomo", "up.browser", "up.link", "blazer", "helio", "hosin",
                "huawei", "novarra", "coolpad", "webos", "techfaith",
                "palmsource", "alcatel", "amoi", "ktouch", "nexian",
                "ericsson", "philips", "sagem", "wellcom", "bunjalloo", "maui",
                "smartphone", "iemobile", "spice", "bird", "zte-", "longcos",
                "pantech", "gionee", "portalmmm", "jig browser", "hiptop",
                "benq", "haier", "^lct", "320x320", "240x320", "176x220",
                "w3c ", "acs-", "alav", "alca", "amoi", "audi", "avan", "benq",
                "bird", "blac", "blaz", "brew", "cell", "cldc", "cmd-", "dang",
                "doco", "eric", "hipt", "inno", "ipaq", "java", "jigs", "kddi",
                "keji", "leno", "lg-c", "lg-d", "lg-g", "lge-", "maui", "maxo",
                "midp", "mits", "mmef", "mobi", "mot-", "moto", "mwbp", "nec-",
                "newt", "noki", "oper", "palm", "pana", "pant", "phil", "play",
                "port", "prox", "qwap", "sage", "sams", "sany", "sch-", "sec-",
                "send", "seri", "sgh-", "shar", "sie-", "siem", "smal", "smar",
                "sony", "sph-", "symb", "t-mo", "teli", "tim-", /*"tosh",*/ "tsm-",
                "upg1", "upsi", "vk-v", "voda", "wap-", "wapa", "wapi", "wapp", "mi",
                "wapr", "webc", "winw", "winw", "xda", "xda-", "Googlebot-Mobile"};
        if (request.getHeader("User-Agent") != null) {
            for (String mobileAgent : mobileAgents) {
                if (request.getHeader("User-Agent").toLowerCase().contains(mobileAgent)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 通过外链获取手机归属地。
     * @param phone 手机号码。
     * @return  手机归属地。
     */
    public static String getPhonePlace(final String phone) {
        if (StringUtils.isBlank(phone) || phone.length() != 11) {
            return null;
        }
        try {
            String content = HttpUtils.doGet("http://m.ip138.com/mobile.asp", new HashMap<String, String>() {
                {
                    put("mobile", phone);
                }
            }, "UTF-8");

            Matcher matcher = Pattern.compile("(?is)(<tr><td>卡号归属地</td><td><span>([^<]+)</span></td></tr>)").matcher(content);
            String result = null;
            while (matcher.find()) {
                if (matcher.start(2) > 0) {
                    result = matcher.group(2);
                    result = result.replaceAll("&nbsp;", "#").replace(" ", "#");
                }
            }

            return result;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 校验身份证件号码是否合法。
     *
     * @param idCard 证件号码。
     * @return true：合法；false：不合法。
     */
    public static boolean validateIdCardNumber(String idCard) {
        assert idCard != null;
        boolean matchOr = PATTERN_ID_CARD.matcher(idCard).matches();
        if(!matchOr) {
            return false;
        }
        // ================ 号码的长度 15位或18位 ================
        if (idCard.length() != 18) {
            return false;
        }

        String[] valCodeArr = {"1", "0", "x", "9", "8", "7", "6", "5", "4", "3", "2"};
        String[] wi = {"7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2"};
        String ai = idCard.substring(0, 17);

        // ================ 数字 除最后以为都为数字 ================
        if (!NumberUtils.isNumber(ai)) {
            return false;
        }
        // ================ 出生年月是否有效 ================
        String strYear = ai.substring(6, 10);       // 年份
        String strMonth = ai.substring(10, 12);     // 月份
        String strDay = ai.substring(12, 14);       // 月份
        if (!CommonUtils.isDateformat(strYear + "-" + strMonth + "-" + strDay)) {
            return false;
        }
        GregorianCalendar gc = new GregorianCalendar();
        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd");

        try {
            if ((gc.get(Calendar.YEAR) - Integer.parseInt(strYear)) > 150 || (gc.getTime().getTime() - s.parse(strYear + "-" + strMonth + "-" + strDay).getTime()) < 0) {
                return false;
            }
        } catch (ParseException e) {
            return false;
        }

        if (Integer.parseInt(strMonth) > 12 || Integer.parseInt(strMonth) == 0) {
            return false;
        }
        if (Integer.parseInt(strDay) > 31 || Integer.parseInt(strDay) == 0) {
            return false;
        }
        // ================ 地区码时候有效 ================
        if (AREA_CODES_FOR_ID_CARD.get(ai.substring(0, 2)) == null) {
            return false;
        }
        // ================ 判断最后一位的值 ================
        int totalMulAiWi = 0;
        for (int i = 0; i < 17; i++) {
            totalMulAiWi = totalMulAiWi + Integer.parseInt(String.valueOf(ai.charAt(i))) * Integer.parseInt(wi[i]);
        }
        int modValue = totalMulAiWi % 11;
        String strVerifyCode = valCodeArr[modValue];
        ai = ai + strVerifyCode;

        if (idCard.length() == 18) {
            if (!ai.equalsIgnoreCase(idCard)) {
                return false;
            }
        }
        return true;
    }
    /**
     * 校验身份证件号码是否合法 只匹配二代身份证。
     *
     * @param idCard 证件号码。
     * @return true：合法；false：不合法。
     */
    public static boolean validateIdCardNumberV2(String idCard) {
        assert idCard != null;
        boolean matchOr = PATTERN_ID_CARD_V2.matcher(idCard).matches();
        if(!matchOr) {
            return false;
        }

        String[] valCodeArr = {"1", "0", "x", "9", "8", "7", "6", "5", "4", "3", "2"};
        String[] wi = {"7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2"};
        String ai = idCard.substring(0, 17);

        // ================ 数字 除最后以为都为数字 ================
        if (!NumberUtils.isNumber(ai)) {
            return false;
        }
        // ================ 出生年月是否有效 ================
        String strYear = ai.substring(6, 10);       // 年份
        String strMonth = ai.substring(10, 12);     // 月份
        String strDay = ai.substring(12, 14);       // 月份
        if (!CommonUtils.isDateformat(strYear + "-" + strMonth + "-" + strDay)) {
            return false;
        }
        GregorianCalendar gc = new GregorianCalendar();
        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd");

        try {
            if ((gc.get(Calendar.YEAR) - Integer.parseInt(strYear)) > 150 || (gc.getTime().getTime() - s.parse(strYear + "-" + strMonth + "-" + strDay).getTime()) < 0) {
                return false;
            }
        } catch (ParseException e) {
            return false;
        }

        if (Integer.parseInt(strMonth) > 12 || Integer.parseInt(strMonth) == 0) {
            return false;
        }
        if (Integer.parseInt(strDay) > 31 || Integer.parseInt(strDay) == 0) {
            return false;
        }
        // ================ 地区码时候有效 ================
        if (AREA_CODES_FOR_ID_CARD.get(ai.substring(0, 2)) == null) {
            return false;
        }
        // ================ 判断最后一位的值 ================
        int totalMulAiWi = 0;
        for (int i = 0; i < 17; i++) {
            totalMulAiWi = totalMulAiWi + Integer.parseInt(String.valueOf(ai.charAt(i))) * Integer.parseInt(wi[i]);
        }
        int modValue = totalMulAiWi % 11;
        String strVerifyCode = valCodeArr[modValue];
        ai = ai + strVerifyCode;

        if (idCard.length() == 18) {
            if (!ai.equalsIgnoreCase(idCard)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 校验身份证件出生日期是否满足平台的18岁限制。
     * 18岁前隔一天
     * @param idCard 证件号码。
     * @return true：18岁；false：不满18岁。
     */
    public static boolean isOlderThanEighteen(String idCard) {
        Calendar calendar= Calendar.getInstance();
        calendar.add(Calendar.YEAR,-18);
        Date maxDate=calendar.getTime();
        if( DateUtils.getIntervalByDays(maxDate,DateUtils.parseString(idCard.substring(6,14)+" 00:00:00","yyyyMMdd HH:mm:ss"))>-1){
            return false;
        }else{
            return true;
        }
    }

    /**
     * 校验Email地址是否合法。
     *
     * @param email 邮件地址。
     * @return true：合法；false：不合法。
     */
    public static boolean validateEmail(String email){
        return PATTERN_EMAIL.matcher(email).matches();
    }

    /**
     * 校验QQ号码是否有效。
     *
     * @param qqAccount QQ号。
     * @return true：合法；false：不合法。
     */
    public static boolean validateQQAccount(String qqAccount){
        return PATTERN_QQ.matcher(qqAccount).matches();
    }

    /**
     * 生成 N 个通配符。
     *
     * @param length 通配符的个数。
     * @return 通配符字符串。
     */
    private static String appendWildCard(int length) {
        String s = "";
        for (int i = 0; i < length; i++) {
            s += "*";
        }
        return s;
    }

    /**
     * 显示银行卡号前三4位,隐藏银行卡号的后四位。
     *
     * @param bankCardNumber 银行卡号。
     * @return 隐藏的银行卡号。
     */
    public static String handleBankCard(String bankCardNumber) {
        if (StringUtils.isEmpty(bankCardNumber)) {
            return bankCardNumber;
        }
        if (bankCardNumber.length() > 4) {
            String s = bankCardNumber.substring(0,4);
            String hidStr =s+appendWildCard(bankCardNumber.length() - 8);
            return hidStr + bankCardNumber.substring(bankCardNumber.length() - 4, bankCardNumber.length());
        }
        return bankCardNumber;
    }

    /**
     * 校验用户名真实姓名是否有效正确。
     *
     * @param name 用户真实姓名。
     * @return true：有效；false：无效。
     */
    public static boolean validateRealName(String name) {
        if (StringUtils.isBlank(name)) {
            return false;
        }
        return true;
    }

    /**
     * 处理用户的姓名，除了第一个字其他都变*。如果不是用户名返回三个*。
     *
     * @param name 待处理的用户名。
     * @return 处理后的用户名。
     */
    public static String handleName(String name) {
        if (StringUtils.isNotBlank(name) && validateRealName(name) && name.length() > 0) {
            name = name.substring(0, 1) + appendWildCard(name.length() - 1);
        } else {
            name = "***";
        }
        return name;
    }

    /**
     * 处理用户的身份证件号码，第四个个数字3位数不变其他变为*，如果不是身份证件号码返回四个*。
     *
     * @param idCardNumber 待处理的用户身份证件号码。
     * @return 处理后的用户身份证件号码。
     */
    public static String handleIdCardNumber(String idCardNumber) {
        if (idCardNumber != null && idCardNumber.length() >= 7) {
            idCardNumber = appendWildCard(4) + idCardNumber.substring(4, 7) + appendWildCard(idCardNumber.length() - 7);
            return idCardNumber;
        } else if (idCardNumber != null && idCardNumber.length() < 7) {
            return idCardNumber;
        } else {
            return "****";
        }
    }

    /**
     * 处理手机号，显示前三位和后三位。
     *
     * @param phoneNumber 手机号。
     * @return 脱敏显示。
     */
    public static String handlePhoneNumber(String phoneNumber){
        if (StringUtils.isEmpty(phoneNumber)) {
            return phoneNumber;
        }
        if (phoneNumber.length() > 3) {
            String s = phoneNumber.substring(0,3);
            String hidStr =s+appendWildCard(phoneNumber.length() - 6);
            return hidStr + phoneNumber.substring(phoneNumber.length() - 3, phoneNumber.length());
        }
        return phoneNumber;
    }


    public static void main(String[] args) {

        System.out.println(handleBankCard("6226096555314775"));
        System.out.println(handleBankCard("420683199005064250"));
        System.out.println(handlePhoneNumber("18665958561"));

    }

}
